1 module hip.net.backend.tcp;
2 import hip.network;
3 import hip.api.net.hipnet;
4 
5 version(WebAssembly){}
6 else:
7 import std.socket;
8 
9 /**
10  * Abstracts away different behaviors from Windows - Posix.
11  * Posix throws an exception, while Windows return a client which is not alive.
12  * Params:
13  *   host = The socket which will accept
14  * Returns: Accepted socket or null.
15  */
16 Socket accept2(Socket host)
17 {
18 	Socket ret;
19 	try
20 	{
21 		ret = host.accept();
22 		if(ret is null || !ret.isAlive)
23 			return null;
24 		ret.blocking = false;
25 	}
26 	catch (SocketAcceptException e) ///This error is always thrown by Linux if it is not blocking
27 		return null;
28 	return ret;
29 }
30 
31 class TCPNetwork : INetworkBackend
32 {
33 
34 	Socket hostSocket;
35 	NetConnectStatus _status;
36 	Socket connectSocket;
37 	Socket client;
38 	SocketSet readSet, writeSet, errSet;
39 	void delegate() onConnect;
40 
41 	/**
42 	 * It is useful to send some messages right after connecting the socket.
43 	 * The problem is that sometimes, this data can't be sent.
44 	 * To solve that issue, this data is accumulated, and thus, is sent in either
45 	 * getData or sendData, the one which is called first.
46 	 * After that, accumulated data becomes unused.
47 	 */
48 	ubyte[] accumulatedData;
49 
50 	int connectedSockets;
51 
52 	this()
53 	{
54 		readSet = new SocketSet();
55 		writeSet = new SocketSet();
56 		errSet = new SocketSet();
57 	}
58 
59 	protected ptrdiff_t getEventCount()
60 	{
61 		import hip.util.data_structures:staticArray;
62 
63 		foreach(set; [readSet, writeSet, errSet].staticArray)
64 		{
65 			set.reset();
66 			foreach(sock; [hostSocket, connectSocket, client].staticArray)
67 			{
68 				if(sock !is null)
69 					set.add(sock);
70 			}
71 		}
72 		TimeVal zero;
73 		return Socket.select(readSet, writeSet, null, &zero);
74 	}
75 
76 	Socket getSocketToSendData()
77 	{
78 		if(hostSocket !is null)
79 			return client;
80 		return connectSocket;
81 	}
82 
83 
84 	NetConnectStatus host(NetIPAddress ip)
85 	{
86 		Socket s = new TcpSocket();
87 		hostSocket = s;
88 		s.blocking = false;
89 		// s.setOption(SocketOptionLevel.SOCKET, SocketOption.LINGER, 1);
90 		// s.setOption(SocketOptionLevel.SOCKET, SocketOption.DEBUG, 1);
91 
92 		try
93 		{
94 			s.bind(ip.type == IPType.ipv4 ? new InternetAddress(ip.ip, ip.port) : new Internet6Address(ip.ip, ip.port));
95 			s.listen(1);
96 			if(getEventCount() > 0  && readSet.isSet(s))
97 				client = s.accept2();
98 			return setStatus(client !is null ?  NetConnectStatus.connected : NetConnectStatus.waiting);
99 		}
100 		catch (SocketOSException e) //Error when multiple bindings. Make it a client
101 		{
102 			hostSocket = null;
103 			return _status = NetConnectStatus.disconnected;
104 		}
105 	}
106 
107 
108 	bool isHost() const
109 	{
110 		return hostSocket !is null;
111 	}
112 	uint getConnectionSelfID() const { return 0; }
113 	void targetConnectionID(uint ID) { }
114 	uint targetConnectionID() const { return 0;}
115 
116 	NetConnectStatus connect(NetIPAddress ip, void delegate() onConnect, uint id = NetID.server)
117 	{
118 		import std.socket;
119 		this.onConnect = onConnect;
120 		if(hostSocket is null && connectSocket is null)
121 		{
122 			if(host(ip) == NetConnectStatus.disconnected)
123 			{
124 				Socket s = new TcpSocket();
125 				connectSocket = s;
126 				s.blocking = false;
127 				s.connect(ip.type == IPType.ipv4 ? new InternetAddress(ip.ip, ip.port) : new Internet6Address(ip.ip, ip.port));
128 				setStatus(!wouldHaveBlocked() ?  NetConnectStatus.connected : NetConnectStatus.waiting);
129 			}
130 		}
131 
132 		if(isHost && (client is null || !client.isAlive))
133 		{
134 			if(getEventCount() > 0 && readSet.isSet(hostSocket))
135 				client = hostSocket.accept2();
136 			return setStatus(client !is null ? NetConnectStatus.connected : NetConnectStatus.waiting);
137 		}
138 
139 		if(connectSocket !is null)
140 		{
141 			setStatus(!wouldHaveBlocked() ?  NetConnectStatus.connected : NetConnectStatus.waiting);
142 		}
143 
144 		return status;
145 	}
146 
147 	NetConnectStatus setStatus(NetConnectStatus stat)
148 	{
149 		_status = stat;
150 		switch(stat)
151 		{
152 			case NetConnectStatus.connected:
153 				if(onConnect !is null) onConnect();
154 				break;
155 			default:
156 				break;
157 		}
158 		return stat;
159 	}
160 
161 
162 	bool sendData(ubyte[] data)
163 	{
164 		import std.stdio;
165 		if(status == NetConnectStatus.connected)
166 		{
167 			if(getEventCount() == 0 || !writeSet.isSet(getSocketToSendData))
168 			{
169 				///Accumulates the data if it can't be sent.
170 				accumulatedData~= data.dup;
171 				return false;
172 			}
173 			else
174 				unfillBuffer();
175 			if(getSocketToSendData == client)
176 				writeln("Sending ", data, " to client. ");
177 			else if(getSocketToSendData == connectSocket)
178 				writeln("Sending data to host ");
179 			ptrdiff_t res = getSocketToSendData.send(data);
180 			if(res ==  0|| res == -1) //Socket closed
181 				return false;
182 			return true;
183 		}
184 		return false;
185 	}
186 
187 	/**
188 	 * This function was designed to avoid calling getEventCount().
189 	 * Be sure to only call that function after a getEventCount() function call.
190 	 */
191 	private void unfillBuffer()
192 	{
193 		if(accumulatedData.length == 0)
194 			return;
195 		if(writeSet.isSet(getSocketToSendData))
196 		{
197 			import core.memory;
198 			ubyte[] dataToSend = accumulatedData;
199 			accumulatedData = null;
200 			sendData(dataToSend);
201 			GC.free(dataToSend.ptr);
202 		}
203 	}
204 
205 	size_t getData(ref ubyte[] buffer)
206 	{
207 		if(status == NetConnectStatus.connected)
208 		{
209 			Socket s = getSocketToSendData;
210 			bool canRead = getEventCount() > 0 && readSet.isSet(s);
211 			unfillBuffer();
212 			if(canRead)
213 			{
214 				ptrdiff_t received = s.receive(buffer);
215 				if(received == 0)
216 				{
217 					_status = NetConnectStatus.attemptingReconnect;
218 				}
219 				if(received == -1)
220 					return 0;
221 				return received;
222 			}
223 		}
224 		return 0;
225 	}
226 
227 	void attemptReconnection()
228 	{
229 		if(status == NetConnectStatus.attemptingReconnect)
230 		{
231 			Socket s = getSocketToSendData();
232 			if(s !is null)
233 			{
234 				s.shutdown(SocketShutdown.BOTH);
235 				s.close();
236 			}
237 			if(hostSocket !is null)
238 				client = null;
239 
240 			_status = NetConnectStatus.waiting;
241 		}
242 	}
243 
244 	void disconnect()
245 	{
246 		Socket s = getSocketToSendData;
247 		if(s is null)
248 			return;
249 		s.shutdown(SocketShutdown.BOTH);
250 		s.close();
251 		hostSocket = null;
252 		client = null;
253 		connectSocket = null;
254 		_status = NetConnectStatus.disconnected;
255 	}
256 
257 	NetConnectStatus status() const { return _status; }
258 }